iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0
Modern Web

Laravel 那麼好用還需要自幹框架嗎系列 第 9

Day 09:findRoute() 實作、SymfonyRoute

  • 分享至 

  • xImage
  •  

今天我們繼續來看 findRoute() 的實作。


/**
* Find the route matching a given request.
*
* @param  \Illuminate\Http\Request  $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->events->dispatch(new Routing($request));

$this->current = $route = $this->routes->match($request);

$route->setContainer($this->container);

$this->container->instance(Route::class, $route);

return $route;
}

event dispatch 的部分我們先略過,往下看路由配對的部分。

這邊使用了 $this->routes->match,也就是前面所定義的 RouteCollection 內的 match()

/**
 * Find the first route matching a given request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Routing\Route
 *
 * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
 */
public function match(Request $request)
{
	$routes = $this->get($request->getMethod());

	// First, we will see if we can find a matching route for this current request
	// method. If we can, great, we can just return it so that it can be called
	// by the consumer. Otherwise we will check for routes with another verb.
	$route = $this->matchAgainstRoutes($routes, $request);

	return $this->handleMatchedRoute($request, $route);
}

這裡面 $routes = $this->get($request->getMethod()); 裡面包含了一些預設 Http Method 和檢查的邏輯,不過我們先不細究。我們更關注的是 $this->matchAgainstRoutes() 這段的實作邏輯

protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
{
	[$fallbacks, $routes] = collect($routes)->partition(function ($route) {
		return $route->isFallback;
	});

	return $routes->merge($fallbacks)->first(
		fn (Route $route) => $route->matches($request, $includingMethod)
	);
}

這一段看起來只是很純粹的 Laravel Collection 處理,不過仔細看過之後,我們可以發現到 $route->matches() 這一段才是實際上比對路由的實作項目。我們再往內追蹤看看 matches()

public function matches(Request $request, $includingMethod = true)
{
	$this->compileRoute();

	foreach (self::getValidators() as $validator) {
		if (! $includingMethod && $validator instanceof MethodValidator) {
			continue;
		}

		if (! $validator->matches($this, $request)) {
			return false;
		}
	}

	return true;
}

裡面的 compileRoute() 則是

protected function compileRoute()
{
	if (! $this->compiled) {
		$this->compiled = $this->toSymfonyRoute()->compile();
	}

	return $this->compiled;
}

到這邊,我們終於經過層層解析,發現了 Laravel 是怎麼將使用者的請求對應到正確的路由:原來還是使用了 Symfony 的套件呀!

如果我們更深入的看 toSymfonyRoute() 可以看到 Laravel 所做的一些變化

public function toSymfonyRoute()
{
	return new SymfonyRoute(
		preg_replace('/\{(\w+?)\?\}/', '{$1}', $this->uri()), $this->getOptionalParameterNames(),
		$this->wheres, ['utf8' => true],
		$this->getDomain() ?: '', [], $this->methods
	);
}

雖然加上了這些變化,不過這其實不脫離 SymfonyRoute 原本設計的初衷,並且免去了 Laravel 開發時需要自己撰寫針對封包做字串處理的部分,像是 SymfonyRoute 需要實作的

private function extractInlineDefaultsAndRequirements(string $pattern): string
{
	if (false === strpbrk($pattern, '?<')) {
		return $pattern;
	}

	return preg_replace_callback('#\{(!?)([\w\x80-\xFF]++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
		if (isset($m[4][0])) {
			$this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null);
		}
		if (isset($m[3][0])) {
			$this->setRequirement($m[2], substr($m[3], 1, -1));
		}

		return '{'.$m[1].$m[2].'}';
	}, $pattern);
}

利用 Symfony 事先定義的處理,我們就可以省略這一段的解析,而是直接設計我們想要的路由結構了,是不是比想像中要簡單很多呢?


上一篇
Day 08:Laravel 怎麼處理請求
下一篇
Day 10:runRoute() 實作
系列文
Laravel 那麼好用還需要自幹框架嗎18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言